package com.xxlie.auth.security.auth.ajax;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.xxlie.auth.domain.User;
import com.xxlie.auth.event.LoginApplicationEvent;
import com.xxlie.auth.security.WebSecurityUtils;
import com.xxlie.auth.security.auth.JwtSigner;
import com.xxlie.auth.security.config.JwtSettings;
import com.xxlie.auth.security.model.SecurityToken;
import com.xxlie.auth.security.model.UserContext;
import com.xxlie.auth.security.model.token.JwtToken;
import com.xxlie.auth.security.model.token.JwtTokenFactory;
import com.xxlie.auth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxlie.core.model.View;

/**
 * AjaxAwareAuthenticationSuccessHandler
 *
 * @author xxlie.com
 * @since 2017/7/30
 */
@Component
public class AjaxAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private UserService userService;
    private final ObjectMapper mapper;
    private final JwtTokenFactory tokenFactory;
    private final JwtSettings settings;

    @Autowired
    public AjaxAwareAuthenticationSuccessHandler(final ObjectMapper mapper, final JwtTokenFactory tokenFactory, final JwtSettings settings) {
        this.mapper = mapper;
        this.tokenFactory = tokenFactory;
        this.settings = settings;
    }

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

        View<SecurityToken> view = getSecurityToken(authentication);

        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        mapper.writeValue(response.getWriter(), view);

        clearAuthenticationAttributes(request);
    }

    public View<SecurityToken> getSecurityToken(Authentication authentication) {
        UserContext userContext = (UserContext) authentication.getPrincipal();

        User user = userService.getByUsername(userContext.getUsername()).orElseThrow(() -> new UsernameNotFoundException("User not found: " + userContext.getUsername()));
        userContext.setUser(user);

        Map<String, Object> extraParams = userContext.getExtraParams();
        if (extraParams != null) {
            String deviceId = String.valueOf(extraParams.get("deviceId"));
            user.setDeviceId(deviceId);
        }

        WebAuthenticationDetails wauth = (WebAuthenticationDetails) authentication.getDetails();
        userService.updateUserLoginInfo(wauth.getRemoteAddress(), user.getId());

        String secret = JwtSigner.generateSecret();
        Map<String, Object> params = new HashMap<>();
        params.put(WebSecurityUtils.X_ALGORITHM, WebSecurityUtils.HMAC_SHA_512);
        params.put(WebSecurityUtils.X_SECRET, secret);

        JwtToken accessToken = tokenFactory.createAccessJwtToken(userContext, params);
        JwtToken refreshToken = tokenFactory.createRefreshToken(userContext, params);

        SecurityToken securityToken = new SecurityToken(accessToken.getToken(), refreshToken.getToken(),
                WebSecurityUtils.HMAC_SHA_512, secret, settings.getTokenExpirationTime());

        View<SecurityToken> view = View.ofOk("登录成功");

        //增加用户的头像、用户名、昵称
        securityToken.setUserId(user.getId());
        securityToken.setUsername(user.getUsername());
        securityToken.setNickName(user.getNickName());
        securityToken.setAvatar(user.getAvatar());

        //处理到返回中
        view.setData(securityToken);

        //调用登录事件
        applicationEventPublisher.publishEvent(new LoginApplicationEvent(this, user));
        return view;
    }

    /**
     * Removes temporary authentication-related data which may have been stored
     * in the session during the authentication process..
     */
    private void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        if (session == null) {
            return;
        }

        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    }
}
